/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.web.npmpackage.core.npminstall;

import com.inspur.edp.web.common.constant.FrontendProjectConstant;
import com.inspur.edp.web.common.entity.CommandExecutedResult;
import com.inspur.edp.web.common.environment.ExecuteEnvironment;
import com.inspur.edp.web.common.environment.checker.ExecuteEnvironmentCheckResult;
import com.inspur.edp.web.common.environment.checker.ExecuteEnvironmentChecker;
import com.inspur.edp.web.common.io.FileUtility;
import com.inspur.edp.web.common.io.NodejsFunctionUtility;
import com.inspur.edp.web.common.logger.WebLogger;
import com.inspur.edp.web.common.utility.*;
import com.inspur.edp.web.npmpackage.api.constant.I18nMsgConstant;
import com.inspur.edp.web.npmpackage.api.entity.CheckNpmPackageJsonParam;
import com.inspur.edp.web.npmpackage.api.entity.NpmInstallParameter;
import com.inspur.edp.web.npmpackage.api.entity.NpmPackageConstants;
import com.inspur.edp.web.npmpackage.api.entity.NpmPackageResponse;
import com.inspur.edp.web.npmpackage.api.entity.packagejson.NpmPackageJsonDependencyInfo;
import com.inspur.edp.web.npmpackage.api.entity.packagejson.NpmPackageJsonInfo;
import com.inspur.edp.web.npmpackage.api.entity.settings.NpmSettings;
import com.inspur.edp.web.npmpackage.api.entity.settings.NpmUpdatePolicy;
import com.inspur.edp.web.npmpackage.core.npminstall.global.GlobalPackageJsonPathGenerator;
import com.inspur.edp.web.npmpackage.core.npminstall.global.NpmInstallGlobalManager;
import com.inspur.edp.web.npmpackage.core.npmlogin.NpmLoginCommandExecutor;
import com.inspur.edp.web.npmpackage.core.npmlogout.NpmLogoutCommandExecutor;
import com.inspur.edp.web.npmpackage.core.npmpackagecheck.NpmPackageCheck;
import com.inspur.edp.web.npmpackage.core.npmsetting.NpmSettingConvertor;
import com.inspur.edp.web.npmpackage.core.npmsetting.NpmSettingManager;

import java.io.File;
import java.util.Arrays;


/**
 * npm 安装manager  进行npm安装的统一入口
 *
 * @author noah
 */
public class NpmInstallManager {
    private static final Object _lockObj = new Object();

    /**
     * 安装前检测是否可以安装
     *
     * @param npmInstallParameter
     * @return
     */
    public static NpmPackageResponse npmInstallCheck(NpmInstallParameter npmInstallParameter) {
        NpmPackageResponse npmPackageResponse = NpmInstallParameterValidator.validate(npmInstallParameter);
        if (!npmPackageResponse.isSuccess()) {
            return npmPackageResponse;
        }
        // 获取node_modules 要安装的路径
        boolean isUpgradeTool = npmInstallParameter.getExecuteEnvironment().equals(ExecuteEnvironment.UpgradeTool);
        String currentWorkPath = FileUtility.getCurrentWorkPath(isUpgradeTool);

        // 获取待执行安装的node_modules 路径
        String parentNode_ModulesPath = NodeModulesPathGenerator.generatePackageJsonPath(npmInstallParameter, currentWorkPath);

        // 设置对应权限，防止由于无权限而导致恩无法写入问题
        FileUtility.setPermission(parentNode_ModulesPath);

        String lockFilePath = NpmInstallLockFilePathGenerator.generate(parentNode_ModulesPath);

        // 根据server启动时间和当前创建时间来进行比较  如果比server创建时间新 那么自动删除
        long serverStartTime = CommonUtility.getServerStartTime();
        File lockFileInfo = new File(lockFilePath);
        boolean needAutoDeleteLockFile = lockFileInfo.exists() && lockFileInfo.lastModified() <= serverStartTime;

        // 避免多线程同时访问造成
        synchronized (_lockObj) {
            if (needAutoDeleteLockFile) {
                FileUtility.deleteFile(lockFilePath);
            } else {
                if (FileUtility.exists(lockFilePath)) {
                    return NpmPackageResponse.createError(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0028));
                }
            }
        }
        return NpmPackageResponse.create();
    }

    /**
     * 执行默认的安装操作
     *
     * @param isUpgradeTool
     * @return
     */
    public static NpmPackageResponse npmInstallWithDefault(boolean isUpgradeTool, boolean isRuntime, boolean isGlobal) {
        NpmPackageResponse packageResponse;
        if (!isGlobal) {
            NpmSettings npmSettings = NpmSettingManager.getNpmSetting(isUpgradeTool);
            NpmInstallParameter packageParameter = NpmSettingConvertor.convertFromNpmSetting(npmSettings);
            // 依据参数进行默认目录的安装
            if (isRuntime) {
                packageParameter.setExecuteEnvironment(ExecuteEnvironment.Runtime);
            } else {
                packageParameter.setExecuteEnvironment(ExecuteEnvironment.Design);
            }

            packageResponse = npmInstall(packageParameter, false);
        } else {
            packageResponse = NpmInstallGlobalManager.npmInstallWithDefault(isUpgradeTool);
        }
        if (!packageResponse.isSuccess()) {
            WebLogger.Instance.info(packageResponse.getErrorMessage(), NpmInstallManager.class.getName());
        }
        // 不再返回错误信息  如果遇到安装异常 进行提示 而不输出错误
        return NpmPackageResponse.create();
    }

    /**
     * 检查npm包的package.json文件是否符合要求。
     * 本方法通过执行命令行操作来检查当前工作目录下的npm包的package.json文件是否满足JIT工具的版本要求。
     * 如果JIT版本高于1.3.299，则执行JIT工具的check命令，检查node_modules目录下是否存在待更新的包。
     *
     * @param executeEnvironment 执行环境，用于判断是否在升级工具的环境下执行。
     * @return 返回命令执行的结果，包括退出码和可能的错误信息。
     */
    public static CheckNpmPackageJsonParam checkNpmPackageJson(ExecuteEnvironment executeEnvironment) {
        // 判断执行环境是否为升级工具
        boolean isUpgradeTool = executeEnvironment == ExecuteEnvironment.UpgradeTool;
        CommandExecutedResult globalJit;
        CommandExecutedResult executedResult;
        CheckNpmPackageJsonParam packageJsonParam = new CheckNpmPackageJsonParam();
        boolean needCopyPackageJson = false;
        String packageJsonPath = null;
        try {
            ExecuteEnvironmentCheckResult nodeCheckResult = ExecuteEnvironmentChecker.checkGlobalNodeInstalled();
            if (nodeCheckResult.isSuccess()) {
                // 执行命令获取全局JIT工具的版本信息
                globalJit = CommandLineUtility.runCommandWithoutThrows("jit --version");
                String globalPackageJsonPath = GlobalPackageJsonPathGenerator.generate();
                if (globalJit.getExitCode() == 0) {
                    String jitVersion = globalJit.getOutputInfo();
                    if (FileUtility.exists(globalPackageJsonPath)) {
                        String globalPackageJsonContent = FileUtility.readAsString(globalPackageJsonPath);
                        NpmPackageJsonInfo serverPackageJsonInfo = NpmPackageCheck.packageJsonInfoGenerate(globalPackageJsonContent);
                        String globalJitVersion = "";
                        for (NpmPackageJsonDependencyInfo dependency : serverPackageJsonInfo.getDependencies()) {
                            if ("@farris/jit-engine".equals(dependency.getKey())) {
                                globalJitVersion = dependency.getValue();
                                break;
                            }
                        }
                        if (!StringUtility.isNullOrEmpty(globalJitVersion) && globalJitVersion.equals(jitVersion)) {
                            // 获取当前工作路径
                            String currentWorkPath = FileUtility.getCurrentWorkPath(isUpgradeTool);
                            // 获取待执行安装的node_modules 路径
                            String nodeModulesParentPath = NodeModulesPathGenerator.generatePackageJsonPath(executeEnvironment, currentWorkPath);
                            String node_modulesPath = FileUtility.combine(nodeModulesParentPath, NpmPackageConstants.Node_ModulesName);
                            packageJsonPath = FileUtility.combine(nodeModulesParentPath, NpmPackageConstants.PackageJsonName);
                            String lockFilePath = NpmInstallLockFilePathGenerator.generate(nodeModulesParentPath);
                            // 当package.json 和 锁文件都不存在时，代表无人正在生成package.json,从安装盘中复制一份
                            if (!FileUtility.exists(packageJsonPath) && !FileUtility.exists(lockFilePath)) {
                                needCopyPackageJson = true;
                                String serverPackageJsonPath = PackageJsonPathGenerator.generate();
                                // 获取package.json的内容
                                FileUtility.copyFile(serverPackageJsonPath, packageJsonPath, true);
                            }

                            if (FileUtility.exists(node_modulesPath)) {
                                // 构造JIT工具的check命令
                                String args = NodejsFunctionUtility.getJitCommandInServer(isUpgradeTool);// "jit";
                                // 空格可不敢删，删了就成jitcheck了
                                args += " check";
                                args += " --path=" + nodeModulesParentPath;
                                // 执行JIT工具的check命令
                                executedResult = CommandLineUtility.runCommandWithoutThrows(args);
                                // node_modules包需要更新时，exitCode为1
                                if (executedResult.getExitCode() == 1) {
                                    packageJsonParam.setSuccessCode(1);
                                    packageJsonParam.setMessage(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0040,
                                            FrontendProjectConstant.NODE_MODULES_UPDATE_URL));
                                } else if (executedResult.getExitCode() == 0) {
                                    packageJsonParam.setSuccessCode(0);
                                } else {
                                    packageJsonParam.setSuccessCode(-1);
                                }
                            } else {
                                packageJsonParam.setSuccessCode(1);
                                packageJsonParam.setMessage(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0041,
                                        nodeModulesParentPath,
                                        FrontendProjectConstant.NODE_MODULES_UPDATE_URL));
                            }
                        } else {
                            packageJsonParam.setSuccessCode(1);
                            packageJsonParam.setMessage(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0042,
                                    getKnowledgeUrl()));
                        }
                    } else {
                        packageJsonParam.setSuccessCode(-1);
                        packageJsonParam.setMessage("全局目录下package.json文件不存在。");
                    }
                } else {
                    packageJsonParam.setSuccessCode(1);
                    packageJsonParam.setMessage(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0043,
                            getKnowledgeUrl()));
                }
            } else {
                packageJsonParam.setMessage(nodeCheckResult.getErrorMessage());
                packageJsonParam.setSuccessCode(-1);
            }

        } catch (RuntimeException ex) {
            // 捕获运行时异常，设置退出码为-1
            packageJsonParam.setSuccessCode(-1);
        } finally {
            if (needCopyPackageJson && !StringUtility.isNullOrEmpty(packageJsonPath)) {
                FileUtility.deleteFile(packageJsonPath);
            }
        }

        return packageJsonParam;
    }

    private static String getKnowledgeUrl() {
        if (OperatingSystemUtility.isLinux()) {
            return FrontendProjectConstant.LINUX_ENVIRONMENT_BUILD;
        }
        return FrontendProjectConstant.WIN_ENVIRONMENT_BUILD;
    }



    /**
     * 比较两个版本号字符串的大小。
     * 该方法通过将版本号字符串按点号分割成数组，并按顺序比较每个部分的整数值来判断版本号的大小。
     * 如果第一个版本号的某个部分大于第二个版本号的对应部分，则第一个版本号较大。
     * 如果第一个版本号的某个部分小于第二个版本号的对应部分，则第一个版本号较小。
     * 如果所有部分都比较完毕且没有出现不相等的情况，则认为两个版本号相等。
     * 注意：该方法假设版本号的每个部分都是非负整数。
     *
     * @param v1 第一个版本号字符串
     * @param v2 第二个版本号字符串
     * @return 如果第一个版本号大于第二个版本号，则返回true；如果第一个版本号小于第二个版本号，则返回false；
     * 如果两个版本号相等，则返回true（因为只有在所有部分都比较完毕且相等的情况下才会返回true）。
     */
    public static boolean jitVersionCompare(String v1, String v2) {
        // 将版本号字符串按照点号分割成数组
        String[] v1Parts = v1.split("\\.");
        String[] v2Parts = v2.split("\\.");

        // 用一个循环来比较版本号的每个部分
        int i = 0;
        while (i < v1Parts.length && i < v2Parts.length) {
            // 将字符串部分转换为整数进行比较
            int v1Part = Integer.parseInt(v1Parts[i]);
            int v2Part = Integer.parseInt(v2Parts[i]);

            // 如果当前部分的版本号v1大于v2，则v1版本号较大，返回true
            if (v1Part > v2Part) {
                return true;
            }
            // 如果当前部分的版本号v1小于v2，则v1版本号较小，返回false
            else if (v1Part < v2Part) {
                return false;
            }
            // 继续比较下一个部分
            i++;
        }
        // 如果所有部分都比较完毕且没有返回，则认为两个版本号相等或v1版本号较大（因为v2可能还有剩余部分但已无需比较）
        return true;
    }




    /**
     * 执行npm 安装
     *
     * @param npmInstallParameter
     * @return
     */
    public static NpmPackageResponse npmInstall(NpmInstallParameter npmInstallParameter, boolean enableForceUpdate) {
        NpmPackageResponse npmPackageResponse = NpmInstallParameterValidator.validate(npmInstallParameter);
        if (!npmPackageResponse.isSuccess()) {
            return npmPackageResponse;
        }

        if (!enableForceUpdate) {
            if (npmInstallParameter.isOfflineMode()) {
                WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0029), NpmInstallManager.class.getName());
                return npmPackageResponse;
            }
            if (npmInstallParameter.getUpdatePolicy().equals(NpmUpdatePolicy.manual)) {
                WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0030), NpmInstallManager.class.getName());
                return npmPackageResponse;
            }
            if (npmInstallParameter.getUpdatePolicy().equals(NpmUpdatePolicy.never)) {
                WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0031), NpmInstallManager.class.getName());
                return npmPackageResponse;
            }
        }

        // 获取node_modules 要安装的路径
        boolean isUpgradeTool = npmInstallParameter.getExecuteEnvironment() == ExecuteEnvironment.UpgradeTool;
        String currentWorkPath = FileUtility.getCurrentWorkPath(isUpgradeTool);

        // 获取待执行安装的node_modules 路径
        String parentNode_ModulesPath = NodeModulesPathGenerator.generatePackageJsonPath(npmInstallParameter, currentWorkPath);

        String lockFilePath = NpmInstallLockFilePathGenerator.generate(parentNode_ModulesPath);

        // 根据server启动时间和当前创建时间来进行比较  如果比server创建时间新 那么自动删除
        boolean reWithUnFinishedInstall = false;
        long serverStartTime = CommonUtility.getServerStartTime();
        File lockFileInfo = new File(lockFilePath);
        boolean needAutoDeleteLockFile = lockFileInfo.exists() && lockFileInfo.lastModified() <= serverStartTime;
        if (needAutoDeleteLockFile) {
            FileUtility.deleteFile(lockFilePath);
            // 继续执行未完成的安装
            reWithUnFinishedInstall = true;
        }

        // 避免多线程同时访问造成
        synchronized (_lockObj) {
            if (FileUtility.exists(lockFilePath)) {
                return NpmPackageResponse.createError(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0032));
            }
        }
        String packageJsonPathInServer = PackageJsonPathGenerator.generate(currentWorkPath, npmInstallParameter.isMobile());
        if (!FileUtility.exists(packageJsonPathInServer)) {

            String errorMessage = ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0033, packageJsonPathInServer);
            WebLogger.Instance.info(errorMessage, NpmInstallManager.class.getName());
            return NpmPackageResponse.create();
        }


        // node_modules 目录下的package.json 文件是否存在
        String packageJsonPathInNodeModules = FileUtility.combine(parentNode_ModulesPath, NpmPackageConstants.PackageJsonName);
        String nodeModulesPath = FileUtility.combine(parentNode_ModulesPath, "node_modules");
        // 如果不是强制安装 只有在package.json 和node_modules 目录均存在的前提下才判断是否无需安装
        if (!reWithUnFinishedInstall && !enableForceUpdate && FileUtility.exists(packageJsonPathInNodeModules) && FileUtility.exists(nodeModulesPath)) {
            String serverPackageJsonContent = FileUtility.readAsString(packageJsonPathInServer);
            NpmPackageJsonInfo serverPackageJsonInfo = NpmPackageCheck.packageJsonInfoGenerate(serverPackageJsonContent);

            String nodeModulesPackageJsonContent = FileUtility.readAsString(packageJsonPathInNodeModules);
            NpmPackageJsonInfo nodeModulesPackageJsonInfo = NpmPackageCheck.packageJsonInfoGenerate(nodeModulesPackageJsonContent);

            if (serverPackageJsonInfo.equals(nodeModulesPackageJsonInfo)) {
                WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0034), NpmInstallManager.class.getName());
                deleteLockFile(lockFilePath);
                return NpmPackageResponse.create();
            }
        }


        try {

            FileUtility.createFile(lockFilePath);

            // 删除package-lock.json 文件
            deletePackageLockJson(parentNode_ModulesPath);

            // 如果文件目录不存在 那么创建对应的文件目录 拷贝package.json 到目标文件目录
            if (!FileUtility.exists(parentNode_ModulesPath)) {
                FileUtility.createDirectory(parentNode_ModulesPath);
            }
            WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0035), NpmInstallManager.class.getName());

            WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0036), NpmInstallManager.class.getName());


            // 仅在帐号和密码都设置的前提下进行登录操作
            boolean needLogin = !StringUtility.isNullOrEmpty(npmInstallParameter.getUserName()) && !StringUtility.isNullOrEmpty(npmInstallParameter.getPassword());

            if (needLogin) {
                WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0037), NpmInstallManager.class.getName());
                NpmPackageResponse loginPackageResponse = NpmLoginCommandExecutor.execute(npmInstallParameter, currentWorkPath);
                if (!loginPackageResponse.isSuccess()) {
                    deleteLockFile(lockFilePath);
                    return loginPackageResponse;
                }
            }

            // 执行package.json 文件复制
            NpmPackageResponse packageJsonCopyResponse = PackageJsonCopyManager.copy(npmInstallParameter, currentWorkPath, parentNode_ModulesPath);
            if (!packageJsonCopyResponse.isSuccess()) {
                deleteLockFile(lockFilePath);
                return packageJsonCopyResponse;
            }

            // npm install 命令执行
            NpmPackageResponse installPackageResponse = NpmInstallCommandExecutor.execute(npmInstallParameter, currentWorkPath, parentNode_ModulesPath);
            if (!installPackageResponse.isSuccess()) {
                deleteLockFile(lockFilePath);
                // 如果安装失败  移除版本文件
                FileUtility.deleteFile(packageJsonPathInNodeModules);
                return installPackageResponse;
            }

            if (false) {
                // 仅在需要登录的前提下执行登录退出操作
                WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0038), NpmInstallManager.class.getName());
                NpmPackageResponse logoutPackgaeResponse = NpmLogoutCommandExecutor.execute(npmInstallParameter, currentWorkPath);
                if (!logoutPackgaeResponse.isSuccess()) {
                    deleteLockFile(lockFilePath);
                    return logoutPackgaeResponse;
                }
            }
        } catch (Exception ex) {
            deleteLockFile(lockFilePath);
        }


        deleteLockFile(lockFilePath);

        WebLogger.Instance.info(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_NPM_PACKAGE_MSG_0039), NpmInstallManager.class.getName());
        return NpmPackageResponse.create();
    }

    /**
     * 删除锁定文件
     *
     * @param lockFilePath
     */
    private static void deleteLockFile(String lockFilePath) {
        try {
            if (FileUtility.exists(lockFilePath)) {
                FileUtility.deleteFile(lockFilePath);
            }
        } catch (Exception ignored) {
            WebLogger.Instance.info("deleteLockFile failed " + ignored.getMessage());
        }
    }


    /**
     * 删除package-lock.json 文件
     *
     * @param parentNode_ModulesPath
     */
    private static void deletePackageLockJson(String parentNode_ModulesPath) {
        String packageLockJsonPath = FileUtility.combine(parentNode_ModulesPath, NpmPackageConstants.PackageLockJsonName);
        if (FileUtility.exists(packageLockJsonPath)) {
            try {
                FileUtility.deleteFile(packageLockJsonPath);
            } catch (Exception ex) {
                WebLogger.Instance.info("delete package-lock.json failed, the path is " + packageLockJsonPath + " , the stack is " + Arrays.toString(ex.getStackTrace()), NpmInstallManager.class.getName());
            }

        }
    }
}
